gbDebug = True;

import math, subprocess;
try:
  import xbmcgui;
except:
  # For command line testing outside of XBMC
  from FakeXBMCGUI import FakeXBMCGUI;
  xbmcgui = FakeXBMCGUI();

gdsValue_by_sEscape = {
  "\\0": "\0",
  "\\a": "\a",
  "\\b": "\b",
  "\\f": "\f",
  "\\n": "\n",
  "\\t": "\t",
  "\\r": "\r",
  '\\"': '"',
  "\\\\": "\\",
}

gdIgnored_sMessage_by_iMSGCode = {
  # The sMessage here is not used in the code, as these messages are ignored. They are there as a helpful reminder
  # which messages we are ignoring.
  1005: "MakeMKV (version) started",
  3007: "Using direct disc access mode",
  3024: "Complex multiplex encountered - %1 cells and %2 VOBUs have to be scanned. This may take some time, please be patient - it can't be avoided.",
  3025: "Title #%1 has length of %2 seconds which is less than minimum title length of %3 seconds and was therefore skipped",
  3028: "Title #%1 was added (%2 cell(s), %3)",
  5011: "Operation successfully completed",
  5014: "Saving %1 titles into directory %2",
};
gdError_sMessage_by_iMSGCode = {
  5021: "Your MakeMKV installation has expired. Please download a new copy or register.",
};

gasDefaultArguments = ["-r", "--noscan", "--progress=-same", "--directio=true"];

# From \makemkvgui\inc\lgpl\apdefs.h
gdtInfo_xType_and_sDescription_by_iInfoCode = {
  0:  (str,   "unknown"),
  1:  (str,   "type"),
  2:  (str,   "name"), # audio channels
  3:  (str,   "language code"),
  4:  (str,   "language name"),
  5:  (str,   "codec id"),
  6:  (str,   "codec short name"),
  7:  (str,   "codec full name"),
  8:  (int,   "chapter count"),
  9:  (str,   "duration"),
  10: (str,   "size string"),
  11: (int,   "size bytes"),
  12: (str,   "stream type extension"),
  13: (str,   "bitrate"),
  14: (int,   "audio channel count"),
  15: (str,   "angle info"),
  16: (str,   "source file name"),
  17: (int,   "audio sample rate"),
  18: (int,   "audio sample size"),
  19: (str,   "video size"),
  20: (str,   "video aspect ratio"),
  21: (float, "video framerate"),
  22: (str,   "stream flags"),
  23: (str,   "date/time"),
  24: (int,   "original title id"),
  25: (int,   "segment count"),
  26: (str,   "segment map"),
  27: (str,   "output filename"),
  28: (str,   "metadata language code"),
  29: (str,   "metadata language name"),
  30: (str,   "tree info"),
  31: (str,   "panel title"),
  32: (str,   "volume name"),
  33: (int,   "order weight"),
  34: (str,   "output format"),
  35: (str,   "output format description"),
};

def convertInfoValue(iInfoCode, sValue):
  if iInfoCode not in gdtInfo_xType_and_sDescription_by_iInfoCode:
    print "Unknown info code %d: cannot convert %s" % (iInfoCode, repr(sValue));
    return None, sValue;
  xInfoType, sInfoDescription = gdtInfo_xType_and_sDescription_by_iInfoCode[iInfoCode];
  if xInfoType is not str:
    # Sometimes addtional information is provided after a space (as in "video framerate" = "25.0 (25000/1000)")
    sValue = sValue.split(" ", 1)[0];
  try:
    xValue = xInfoType(sValue);
  except ValueError:
    print "Bad format for info code %d(%s): cannot convert %s to %s" % \
        (iInfoCode, sInfoDescription, repr(sValue), repr(xInfoType));
    return None, sValue;
  return sInfoDescription, xValue;

def readCSVLine(oStream):
  asLine = [];
  sValue = "";
  bInValue = False;
  bInString = False;
  sEscape = None;
  while 1:
    sChar = oStream.read(1);
    if not sChar:
      break;
    if sEscape:
      sEscape += sChar;
      if len(sEscape) == 2:
        # Only allow valid escape codes:
        if sEscape in gdsValue_by_sEscape:
          sValue += gdsValue_by_sEscape[sEscape];
        elif not sEscape == "\\x":
          return "Invalid escape %s" % sEscape, asLine;
        sEscape = None;
      elif len(sEscape) == 4:
        try:
          sValue += chr(int(sEscape[2:], 16));
        except ValueError:
          return "Invalid escape %s" % sEscape, asLine;
        sEscape = None;
    elif bInString:
      if sChar == "\\":
        sEscape = "\\";
      elif sChar == '"':
        bInString = False;
      else:
        sValue += sChar;
    else:
      if sChar in "\r\n":
        # We ignore CR/LFs until we have read data, so as to skip empty
        # lines and allow handling of any combination of CR and/or LF to
        # terminate lines.
        if asLine or bInValue:
          break;
      elif sChar == ",":
        asLine.append(sValue);
        sValue = "";
        bInValue = False;
      elif sChar == '"':
        if bInValue:
          return 'Quote (") found inside non-string data', asLine;
        bInString = True;
        bInValue = True;
      else:
        sValue += sChar;
        bInValue = True;
    #loop
  if bInString:
    return "Unterminated string data %s" % sValue;
  if asLine or bInValue:
    asLine.append(sValue);
  return None, asLine;

def runMakeMKVCon(oSettings, sDescription, *tsArgs):
  sMakeMKVConPath = oSettings.get("makemkvcon-path");
  oProgressDialog = xbmcgui.DialogProgress();
  sProgressMessage = "Starting MakeMKV...";
  nTotalProgressPercent = 0.0;
  nCurrentProgressPercent = 0.0;
  sStatusMessage = "";
  iTotalProgress = 0;
  iCurrentProgress = None;
  iMaxProgress = None;

  oStartupInfo = subprocess.STARTUPINFO();
  oStartupInfo.dwFlags |= 1; # STARTF_USESHOWWINDOW, see http://msdn.microsoft.com/en-us/library/windows/desktop/ms686331(v=vs.85).aspx
  oStartupInfo.wShowWindow = 0; #SW_HIDE, see http://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx

  asCommandLine = (
      [sMakeMKVConPath] +
      gasDefaultArguments +
      list(tsArgs)
  );

  oProgressDialog.create(oSettings.sName, sDescription, sProgressMessage, sStatusMessage);
  oMakeMKVConProcess = subprocess.Popen(asCommandLine, stdout=subprocess.PIPE, startupinfo=oStartupInfo);
  sMakeMKVParseError = None;
  asMakeMKVErrorMessages = [];
  aasData = [];
  try:
    while 1:
      if oProgressDialog.iscanceled():
        try:
          oMakeMKVConProcess.terminate();
        except WindowsError, oException:
          pass;
        sLine2 = "Cancelling MakeMKV...";
      else:
        sLine2 = sProgressMessage;
        if nCurrentProgressPercent is not None:
          sLine2 = "%s - %.1f%%" % (sProgressMessage, nCurrentProgressPercent);
      oProgressDialog.update(int(nTotalProgressPercent), sDescription, sLine2, sStatusMessage);
      sError, asLine = readCSVLine(oMakeMKVConProcess.stdout);
      if sMakeMKVParseError or not asLine:
        break;
      sMessageType, asLine[0] = asLine[0].split(":");
      if sMessageType != "PRGV":
        print "%s: MakeMKV: %6s:%s" % (__file__, sMessageType, ",".join(asLine));
      if sMessageType == "MSG":
        iCode = int(asLine.pop(0));
        iFlags = int(asLine.pop(0));
        iParameterCount = int(asLine.pop(0));
        sMessage = asLine.pop(0); # the only thing important
        sFormat = asLine.pop(0);
        asParameters = asLine;
        asLine = [];
        if iCode in gdIgnored_sMessage_by_iMSGCode:
          pass;
        elif iCode in gdError_sMessage_by_iMSGCode:
          asMakeMKVErrorMessages.append(gdError_sMessage_by_iMSGCode[iCode]);
        else:
          asMakeMKVErrorMessages.append(sMessage);
          print "%s: Unhandled MSG %d: %s" % (__file__, iCode, sMessage);
      elif sMessageType in ["PRGC", "PRGT"]:
        bTotal = sMessageType == "PRGT"; # or current
        iCode = int(asLine.pop(0));
        iId = int(asLine.pop(0));
        sName = asLine.pop(0);
        nCurrentProgressPercent = None;
        sProgressMessage = sName;
      elif sMessageType == "PRGV":
        iCurrentProgress = int(asLine.pop(0));
        iTotalProgress = int(asLine.pop(0));
        iMaxProgress = int(asLine.pop(0));
        nCurrentProgressPercent = 100.0 * float(iCurrentProgress) / float(iMaxProgress);
        nTotalProgressPercent = 100.0 * float(iTotalProgress) / float(iMaxProgress);
      elif sMessageType == "DRV":
        iIndex = int(asLine.pop(0));
        iUnknown1 = int(asLine.pop(0));
        iUnknown2 = int(asLine.pop(0));
        iUnknown3 = int(asLine.pop(0));
        sDriveName = asLine.pop(0);
        sDiscName = asLine.pop(0);
        sDeviceName = asLine.pop(0);
        # ignored - "--noscan" seems to have no effect :(
      elif sMessageType == "TCOUNT":
        iTitles = int(asLine.pop(0));
        aasData.append((sMessageType, iTitles));
        sStatusMessage = "Found %d titles." % iTitles;
#        if gbDebug:
#          print "%s: Found %d titles." % (__file__, iTitles);
      elif sMessageType == "CINFO":
        iInfoCode = int(asLine.pop(0));
        iLanguageId = int(asLine.pop(0));
        sInfo = asLine.pop(0);
        sInfoDescription, xValue = convertInfoValue(iInfoCode, sInfo);
        if sInfoDescription:
          aasData.append((sMessageType, sInfoDescription, iLanguageId, xValue));
#          if gbDebug:
#            print "%s: Disc info: %s = %s." % (__file__, repr(sInfoDescription), repr(xValue));
#        else:
#          print "%s: Disc info: unknown code %s = %s." % (__file__, repr(iInfoCode), repr(sValue));
        sStatusMessage = "Processing disc...";
      elif sMessageType == "TINFO":
        iTitleId = int(asLine.pop(0));
        iInfoCode = int(asLine.pop(0));
        iLanguageId = int(asLine.pop(0));
        sInfo = asLine.pop(0);
        sInfoDescription, xValue = convertInfoValue(iInfoCode, sInfo);
        if sInfoDescription:
          aasData.append((sMessageType, iTitleId, sInfoDescription, iLanguageId, xValue));
#          if gbDebug:
#            print "%s: Title #%d info: %s = %s." % (__file__, iTitleId, repr(sInfoDescription), repr(xValue));
#        else:
#          print "%s: Title #%d unknown code %s = %s." % (__file__, iTitleId, repr(iInfoCode), repr(sValue));
        sStatusMessage = "Processing title #%d..." % iTitleId;
      elif sMessageType == "SINFO":
        iTitleId = int(asLine.pop(0));
        iStreamId = int(asLine.pop(0));
        iInfoCode = int(asLine.pop(0));
        iLanguageId = int(asLine.pop(0));
        sInfo = asLine.pop(0);
        sInfoDescription, xValue = convertInfoValue(iInfoCode, sInfo);
        if sInfoDescription:
          aasData.append((sMessageType, iTitleId, iStreamId, sInfoDescription, iLanguageId, xValue));
#          if gbDebug:
#            print "%s: Title #%d, stream #%d info: %s = %s." % \
#                (__file__, iTitleId, iStreamId, repr(sInfoDescription), repr(xValue));
#        else:
#          print "%s: Title #%d, stream #%d unknown code %s = %s." % \
#              (__file__, iTitleId, iStreamId, repr(iInfoCode), repr(sValue));
        sStatusMessage = "Processing title #%d, stream #%d..." % (iTitleId, iStreamId);
      else:
        print "%s: Unknown message type %s ignored (data: %s)" % (__file__, sMessageType, repr(asLine));
      if asLine:
        print "%s: Message %s has additional data: %s" % (__file__, sMessageType, repr(asLine));
  finally:
    oProgressDialog.close();
    if oMakeMKVConProcess.poll() is None:
      print "%s: Killing MakeMKV..." % __file__;
      try:
        oMakeMKVConProcess.kill();
      except WindowsError, oException:
        pass;
      print "%s: Waiting for MakeMKV to die..." % __file__;
  iErrorCode = oMakeMKVConProcess.wait();
  if iErrorCode != 0:
    print "%s: MakeMKV error code: %d" % (__file__, iErrorCode);
  if sMakeMKVParseError:
    print "*** Error in MakeMKV output: %s" % sMakeMKVParseError;

  if oProgressDialog.iscanceled():
    return False, True, aasData;

  if sMakeMKVParseError:
    oDialog = xbmcgui.Dialog();
    oDialog.ok(oSettings.sName, "Error parsing MakeMKV output:", sError);
    return True, False, aasData;

  if iErrorCode != 0:
    oDialog = xbmcgui.Dialog();
    if not asMakeMKVErrorMessages:
      oDialog.ok(oSettings.sName, "MakeMKV reported error code %d, but no error messages were detected." % iErrorCode);
    elif len(asMakeMKVErrorMessages) < 2:
      oDialog.ok(oSettings.sName, "MakeMKV reported error code %d:" % iErrorCode, *asMakeMKVErrorMessages);
    else:
      oDialog.select("MakeMKV reported error code %d:" % iErrorCode, asMakeMKVErrorMessages);
    return True, False, aasData;
  
  return False, False, aasData;
